import { Asset, AssetIndex, ClientJar, Java, Library } from "./download";
import { ClientParseError, GameLaunchError } from "./error";

import {quote as shellEscape} from "shell-quote";

import { IProfile } from "./profile";
import { RuleContent, Rules } from "./rule";
import Constants from "./constant";
import { dirname, join as joinPath } from "path";
import { formatArguments, shellQuoteWindows } from "./template";

interface IMinecraftVersion {
    /**
     * launch the game.
     * @param profile the player's profile.
     * @throws { GameLaunchError } error(s) happened when launching the game.
     */
    launch(
        java:Java,
        profile: IProfile,
        {
            quickPlayRealms,
            quickPlayMultiplayer,
            quickPlaySinglePlayer,
            quickPlayPath,
            isQuickPlayRealms,
            isQuickPlayMultiPlayer,
            isQuickPlaySinglePlayer,
            hasCustomResolution,
            resolutionWidth,
            resolutionHeight,
            hasQuickPlaysSupport,
        }: LaunchArguments):void;

    get id(): string;
    get path(): string;
}

type LaunchArguments = {
    quickPlayRealms: string | null | undefined | void;
    quickPlayMultiplayer: string | null | undefined | void;
    quickPlaySinglePlayer: string | null | undefined | void;
    isQuickPlayRealms: boolean | null | undefined | void;
    isQuickPlayMultiPlayer: boolean | null | undefined | void;
    isQuickPlaySinglePlayer: boolean | null | undefined | void;
    quickPlayPath: string | null | undefined | void;
    hasCustomResolution: string | null | undefined | void;
    resolutionWidth: string | null | undefined | void;
    resolutionHeight: string | null | undefined | void;
    hasQuickPlaysSupport: boolean | null | undefined | void
};

class ModernVersion implements IMinecraftVersion {
    id: string = "";
    path: string = "";
    gamePath:string = "";

    mainClass: string = "";
    clientVersion: string = "";

    libraries: Library[] = [];
    natives: Library[] = [];
    assetIndex: AssetIndex | undefined;
    clientJar: ClientJar | undefined;

    gameArguments:any[]=[];
    jvmArguments:any[]=[];

    constructor() {}

    private getClasspath():string {
        if (!this.clientJar) throw Error("no clientJar found. USE FACTORY TO CREATE VERSION!!!");

        const result:string[] = [];
        
        this.libraries.forEach(library => {
            result.push(library.diskLocations[0]);
        });

        result.push(this.clientJar.diskLocations[0]);

        return result.join(";");
    }

    launch(
        java:Java,
        profile: IProfile,
        {
            quickPlayRealms,
            quickPlayMultiplayer,
            quickPlaySinglePlayer,
            quickPlayPath,
            isQuickPlayRealms,
            isQuickPlayMultiPlayer,
            isQuickPlaySinglePlayer,
            hasCustomResolution,
            resolutionWidth,
            resolutionHeight,
            hasQuickPlaysSupport,
        }:LaunchArguments):string {
            if (!this.clientJar) throw Error("no clientJar found. USE FACTORY TO CREATE VERSION!!!");

        const javaPath = java.diskLocations[0];

        const rules:RuleContent = Object.assign({
            features: {
                has_custom_resolution: hasCustomResolution,

                has_quick_plays_support: hasQuickPlaysSupport,
                is_quick_play_singleplayer: isQuickPlaySinglePlayer,
                is_quick_play_multiplayer: isQuickPlayMultiPlayer,
                is_quick_play_realms: isQuickPlayRealms
            }
        }, Constants.osRules);

        const templateData = {
            auth_player_name: profile.authPlayerName,
            version_name: this.id,
            game_directory: this.gamePath,
            assets_root: joinPath(this.gamePath, "assets"),
            auth_uuid: profile.UUID,
            auth_access_token: profile.accessToken,
            clientId: "whats this?",
            user_type: profile.userType,
            versionType: Constants.launcherName,
            version_type: Constants.launcherName,
            assets_index_name: this.assetIndex?.id,
            resolution_width: resolutionWidth,
            resolution_height: resolutionHeight,
            quickPlayPath,
            quickPlaySinglePlayer,
            quickPlayMultiplayer,
            quickPlayRealms,
            launcher_name: Constants.launcherName,
            launcher_version: Constants.launcherVersion,
            natives_directory: joinPath(this.path, "natives"),
            classpath: this.getClasspath()
        };

        const command = `${(rules.os.name=="windows" ? shellQuoteWindows : shellEscape)([javaPath])} -Dminecraft.client.jar=${(rules.os.name=="windows" ? shellQuoteWindows : shellEscape)([this.clientJar.diskLocations[0]])} ${formatArguments(this.jvmArguments, templateData, rules)} ${this.mainClass} ${formatArguments(this.gameArguments, templateData, rules)}`;
        return command;
    }
}

class ModernVersionFactory {
    instance: ModernVersion = new ModernVersion();
    client: any;
    gamePath: string = "";

    /**
     * create a ModernVersion instance.
     * @param clientJSON the text content of the client.json.
     * @throws { ClientParseError } if error(s) happened when parsing.
     */
    create(clientJSONPath: string, gamePath: string): ModernVersion {
        let client: any;
        this.gamePath = this.instance.gamePath = gamePath;

        try {
            client = this.client = require(clientJSONPath);
        } catch (e: any) {
            throw new ClientParseError(
                e?.cause || "syntax error in the client.json",
            );
        }
        this.instance.path = dirname(clientJSONPath);

        // version basic info
        if (!client.mainClass || typeof client.mainClass != "string")
            throw new ClientParseError("no valid mainclass");
        this.instance.mainClass = client.mainClass;

        if (!client.clientVersion || typeof client.clientVersion != "string")
            throw new ClientParseError("no valid clientVersion");
        this.instance.clientVersion = client.clientVersion;

        if (!client.id || typeof client.id != "string")
            throw new ClientParseError("no valid id");
        this.instance.id = client.id;

        this.parseClientJar();
        this.parseAssetIndex();
        this.parseLibraries();

        try {
            this.instance.gameArguments = client.arguments.game;
            this.instance.jvmArguments = client.arguments.jvm;
        }

        catch {
            throw new ClientParseError("no arguments");
        }

        return this.instance;
    }

    parseClientJar(): void {
        if (!this.client?.downloads?.client)
            throw new ClientParseError("no valid client jar");
        const clientJarRaw = this.client.downloads.client;

        if (!clientJarRaw.sha1 || !clientJarRaw.url)
            throw new ClientParseError("no valid client jare");
        this.instance.clientJar = new ClientJar(
            clientJarRaw.url,
            clientJarRaw.sha1,
            this.instance,
        );
    }

    parseAssetIndex(): void {
        if (
            !this.client.assetIndex ||
            !this.client.assetIndex.id ||
            !this.client.assetIndex.sha1 ||
            !this.client.assetIndex.url
        )
            throw new ClientParseError("no valid assetindex");

        this.instance.assetIndex = new AssetIndex(
            this.client.assetIndex.id,
            this.client.assetIndex.sha1,
            this.client.assetIndex.url,
            joinPath(this.gamePath, "assets"),
        );
    }

    parseLibraries(): void {
        if (!this.client.libraries || !Array.isArray(this.client.libraries))
            throw new ClientParseError("no valid libraries");

        const libraries: Library[] = [];
        const librariesPath = joinPath(this.gamePath, "libraries");

        this.client.libraries.forEach((library: any) => {
            if (!library["name"] || !(typeof library["name"] == "string"))
                return;
            if (!library["downloads"]) return;

            if (library["rules"] && Array.isArray(library["rules"])) {
                const rules = Rules.parseRules(library["rules"]);
                if (!Rules.check(rules, Constants.osRules)) return;
            }

            if (library["downloads"]["artifact"]) {
                const rawLibrary = library["downloads"]["artifact"];
                if (rawLibrary.path || !rawLibrary.sha1 || !rawLibrary.url) {
                    libraries.push(
                        new Library(
                            rawLibrary.url,
                            joinPath(librariesPath, rawLibrary.path),
                            rawLibrary.sha1,
                        ),
                    );
                }
            }

            if (library["natives"] && library["downloads"]["classifiers"]) {
                if (
                    library["natives"][
                        Constants.osRules["os"]["name"]
                    ]
                ) {
                    const rawLibrary =
                        library["downloads"]["classifiers"][
                            library["natives"][
                                Constants.osRules["os"]["name"]
                            ]
                        ];
                    if (
                        rawLibrary.path ||
                        !rawLibrary.sha1 ||
                        !rawLibrary.url
                    ) {
                        const library = new Library(
                            rawLibrary.url,
                            joinPath(librariesPath, rawLibrary.path),
                            rawLibrary.sha1,
                        );
                        libraries.push(library);

                        this.instance.natives.push(library);
                    }
                }
            }
        });

        this.instance.libraries = libraries;
    }
}

export { IMinecraftVersion, ModernVersion, ModernVersionFactory, LaunchArguments };
